package hotel.quartz.oversea.job.switchs;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import hotel.base.oversea.constant.ApiConstans;
import hotel.base.oversea.constant.CommonConstants;
import hotel.base.oversea.constant.CommonUrlConstants;
import hotel.base.oversea.constant.SwitchConstants;
import hotel.base.oversea.constant.quartz.UrlSwitchsEnums;
import hotel.base.oversea.utils.Result;
import hotel.quartz.oversea.config.service.RedisService;
import hotel.quartz.oversea.entity.form.JobForm;
import hotel.quartz.oversea.job.base.BaseJob;
import hotel.quartz.oversea.service.JobAndTriggerService;
import hotel.quartz.oversea.util.MapUtils;
import hotel.quartz.oversea.vo.switchs.GetMasterHotelListVo;
import hotel.quartz.oversea.vo.switchs.SwitchMasterHotelVo;
import hotel.quartz.oversea.util.JobRestTemplateUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * <p>
 *  Switchs Hotel Master Job
 * </p>
 *
 * @description: Switchs Hotel Master Job
 * @author: qiuyy
 * @date: 2020-05-26 13:22
 * @version: V1.0
 */
@Slf4j
public class SwitchHotelMasterJob implements BaseJob {
    private String  jobGroupName = SwitchConstants.JOB_GROUP_NAME_SWITCHS;
    private String jobClassName = this.getClass().getName();
    private String timestamp = "Shmj"+String.valueOf(System.currentTimeMillis());
    private int pageSize = ApiConstans.SW_PAGE_SIZE;
    private int thisRedisCount = SwitchConstants.REDIS_SW_MASTER_HOTEL_WAIT_COUNT;
    private String redisWaitSaveKey = SwitchConstants.REDIS_SW_MASTER_HOTEL_WAIT_SAVE;
    private String redisSwCityIds = SwitchConstants.REDIS_SWITCH_CITY_IDS;
    private String redisSwCityIdsHas = SwitchConstants.REDIS_SWITCH_CITY_IDS_HAS;
    private String getRedisSwCityIdNow = SwitchConstants.REDIS_SWITCH_CITY_ID_NOW;
    private String redisKeyHeadCityPageIndex = "quartz_master_hotel_job_city_page_index_";


    @Autowired
    private JobAndTriggerService jobAndTriggerService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisService redisService;
    @Autowired
    private RestTemplate restTemplate;


    /**
     *   母酒店数据，每十秒跑一次（接口要求：每分钟调用不得超过10次，分页获取）
     *   定时器执行：某个城市的某页酒店列表数据
     *
     *   定时器结束标志：
     *   是否所有城市都查询完，所有城市都要查询完酒店
     *
     *   条件：某个城市，缓存到redis的城市ids
     *   条件：某个城市的页码，第三方参数pageIndex页码，pageIndex>0
     *   结果：response数据可能为空，为空不代表 异常，因为是页码查询，最后一页可能为空
     *
     */
    @Override
    @SneakyThrows
    public void execute(JobExecutionContext jobExecutionContext) {
        //endingJob(true);
        log.info("Switchs Hotel Master Job {} >>>>>>>> 携程母酒店获取执行时间: {}",timestamp, DateUtil.now());
        String accessToken = "";
        //String host = CommonUrlConstants.SWITCHS_HOST;
        String host= CommonUrlConstants.SWITCHS_ONLINE_HOST;
        String cityHotelUrl = host + UrlSwitchsEnums.masterHotel.GET_HOTEL_BY_CICY.getValue();// + "?access_token="+accessToken;
        String saveHotelUrl = host + UrlSwitchsEnums.masterHotel.SAVE.getValue();// + "?access_token="+accessToken;
        boolean check = checkSaveRedisData(saveHotelUrl);
        if(!check){
            //当前需查询的城市List，获取城市ID：判断如果已无城市id数据返回则当前定时器可以停止parse
            int cityId = oneCity();
            if(-1 != cityId){
                //当前需查询的城市的酒店页码pageIndex
                String someHotelPageIndex = getOneCitySomeHotelPageIndex(cityId);
                //获取当前城市的酒店页码数据，根据cityId和页码pageIndex
                requestOneCitySomeHotel(cityId,someHotelPageIndex,cityHotelUrl,saveHotelUrl,accessToken);
            }
        }
        log.info("Switchs Hotel Master Job {} >>>>>>>> 携程母酒店获取结束时间: {}",timestamp, DateUtil.now());
    }

    /**
     * 校验是否需要保存缓存的数据
     * @param saveUrl
     * @return
     */
    private boolean checkSaveRedisData(String saveUrl) {
        log.info("Switchs Hotel Master Job {} 携程母酒店定时器进入方法 checkSaveRedisData",timestamp);
        Map<String, List<SwitchMasterHotelVo>> getInfos = getRedisWaitData();
        if(null != getInfos && getInfos.size()>=thisRedisCount){
            //缓存酒店数据达到大于等于thisRedisCount，则执行保存到数据库操作，可能大于thisRedisCount比如数据操作保存失败还原加入到redis
            boolean result = requestSaveWaitDataApi(saveUrl,getInfos);
            if(result){
                return true;
            }else{
                return false;
            }
        }else{
            return false;
        }
    }
    /**
     * 调用保存缓存数据集合接口
     * @param saveUrl
     * @param redisWaitDataSaveMap
     * @return
     */
    private boolean requestSaveWaitDataApi(String saveUrl,Map<String,List<SwitchMasterHotelVo>> redisWaitDataSaveMap) {
        log.info("Switchs Hotel Master Job {} 携程母酒店定时器进入方法 requestSaveWaitDataApi",timestamp);

        if(null == redisWaitDataSaveMap || redisWaitDataSaveMap.size()==0){
            log.warn("Switchs Hotel Master Job {} 携程母酒店定时器进入方法 requestSaveWaitDataApi 待保存数据redisWaitDataSaveMap为空",timestamp);
            return true;
        }

        Map map = new HashMap();
        map.put("dltMasterHotelEntityListMap",redisWaitDataSaveMap);
        //调用前,删除当前缓存的酒店数据，让下一次定时器正常查询上游接口。若调用结果失败则还原加入缓存
        deleteRedisWaitSaveData();
        //发起请求
        Result response = JobRestTemplateUtils.post(saveUrl, JSON.toJSONString(map),restTemplate);
        if(response.getCode()== CommonConstants.SUCCESS){
            log.info("Switchs Hotel Master Job {} requestSaveWaitDataApi 保存数据成功",timestamp);
            return true;
        }else{
            log.error("Switchs Hotel Master Job {} requestSaveWaitDataApi 异常错误: {}",timestamp,response);
            backPutRedisWaitSaveData(redisWaitDataSaveMap);
            return false;
        }
    }
    /**
     * 删除缓存的待保存数据
     * @return
     */
    public void deleteRedisWaitSaveData(){
        log.info("Switchs Hotel Master Job {} 携程母酒店定时器进入方法 deleteRedisWaitSaveData 删除当前携程母酒店定时器数据缓存",timestamp);
        redisTemplate.delete(redisWaitSaveKey);
    }
    /**
     * 还原追加回去待保存数据
     * @return
     */
    public void backPutRedisWaitSaveData(Map<String,List<SwitchMasterHotelVo>> redisWaitDataMap){
        log.info("Switchs Hotel Master Job {} 携程母酒店定时器进入方法 backPutRedisWaitSaveData 还原追加当前酒店信息缓存 ",timestamp);
        //还原redis数据
        Map<String,List<SwitchMasterHotelVo>> getRedisMap = (Map<String,List<SwitchMasterHotelVo>>)redisService.get(redisWaitSaveKey);
        if(null != getRedisMap && getRedisMap.size()>0){
            getRedisMap.putAll(redisWaitDataMap);
        }else{
            getRedisMap = redisWaitDataMap;
        }
        setPutRedisWaitData(getRedisMap);
    }
    /**
     * 获取保存在缓存中的待save的数据
     */
    private Map<String,List<SwitchMasterHotelVo>> getRedisWaitData() {
        return (Map<String,List<SwitchMasterHotelVo>>)redisService.get(redisWaitSaveKey);
    }

    /**
     * 获取缓存符合条件的查询城市ID
     * 加入 setRedisCityIdNow
     * 加入 putHasCityMap
     * 一拿出来一个城市id就：
     * 1、加入hasCity  暂定为当前城市已查询完酒店数据
     * 2、后续请求上游接口后：判断是否需要移除hascity：需判断当 a  存在下一页数据，当前city还需要被查询，则移除hascity，并set当前城市pageIndex   b  当前city请求接口发送异常，需要还原则移除hascity
     *
     * @return
     */
    public int oneCity(){
        String nowCityId = getRedisCityIdNow();
        if(null == nowCityId){
            //重新获取
            Map<String,Object> allCityIdsMap = getRedisAllCityIdsMap();
            if(null != allCityIdsMap && allCityIdsMap.size()>0){
                Map<String,Object> hasCityIdsMap = getRedisHasCityIdsMap();
                if(null == hasCityIdsMap){
                    for (Map.Entry<String, Object> entry : allCityIdsMap.entrySet()) {
                        nowCityId = entry.getKey();
                        break;
                    }
                }else{
                    Map<String, Object> resultMap = MapUtils.getDifferenceSet(allCityIdsMap,hasCityIdsMap);
                    if(null != resultMap && resultMap.size()>0){
                        for (Map.Entry<String, Object> entry : resultMap.entrySet()) {
                            nowCityId = entry.getKey();
                            break;
                        }
                    }else{
                        log.info("Switchs Hotel Master Job {} 携程母酒店定时器 getDifferenceSet is null",timestamp);
                        endingJob(true);
                        return -1;
                    }
                }
                //预先把当前城市加入已完成查询，后面查询出现问题则需要还原移出“hasCityMap”
                putHasCityMap(nowCityId);
                setRedisCityIdNow(nowCityId);
            }else{
                endingJob(false);
                log.error("Switchs Hotel Master Job 定时器程序暂停！携程城市ids获取异常错误,redis allCityIdsMap is null");
                return -1;
            }
        }else{
            //该城市还存在翻页酒店列表数据
            return Integer.valueOf(nowCityId);
        }
        return Integer.valueOf(nowCityId);
    }

    /**
     * 查询指定城市的某些酒店列表数据：根据城市ID，页码index定位
     * @param cityId
     * @param pageIndex
     * @param cityHotelUrl
     * @param accessToken
     */
    public void requestOneCitySomeHotel(int cityId,String pageIndex,String cityHotelUrl,String saveUrl,String accessToken){
        String checkResponse = doRequestOneCitySomeHotel(saveUrl,pageIndex,cityId,cityHotelUrl,accessToken);
        if(checkResponse.equals("next")){
            //当前城市有下一页，让定时器下一次继续跑当前城市getRedisCityIdNow()，当前城市移出hasCity，更新pageIndex
            removeHasCityMap(getRedisCityIdNow());
            updateNextPage(cityId);
        }else if(checkResponse.equals("error")){
            //请求异常或其他错误，需还原当前操作，
            //移出已查询HasCityMap，让定时器下一次继续跑当前城市getRedisCityIdNow()+当前getOneCitySomeHotelPageIndex()不做改动
            removeHasCityMap(getRedisCityIdNow());
        }else if(checkResponse.equals("none")){
            //无数据没下一页，redis数据操作保存，保存成功后暂停定时器
            noneDataUpdateDeleteRedisHasCityMap(saveUrl);
        }
    }
    private String doRequestOneCitySomeHotel(String saveUrl,String pageIndex,Integer cityId,String cityHotelUrl,String accessToken) {
        GetMasterHotelListVo getMasterHotelListVo = new GetMasterHotelListVo();
        getMasterHotelListVo.setCityID(cityId);
        getMasterHotelListVo.setMasterHotelID(0);//0表示不传
        getMasterHotelListVo.setPageIndex(Integer.valueOf(pageIndex));//需大于0
        getMasterHotelListVo.setPageSize(pageSize);
        String paramsJson = JSON.toJSONString(getMasterHotelListVo);
        log.info("Switchs Hotel Master Job {} 携程母酒店更新进入方法 doRequestOneCitySomeHotel : {},{}",timestamp,paramsJson,accessToken);
        //发起请求
        Result response = JobRestTemplateUtils.post(cityHotelUrl,paramsJson,restTemplate);
        if(response.getCode()== CommonConstants.SUCCESS){
            if(null!= response.getData() && null != JSON.parseObject(JSON.toJSONString(response.getData())).getString("dltMasterHotelEntityList")){
                //解析响应结果
                String datajson = JSON.toJSONString(response.getData());
                String hotelStr = JSON.parseObject(datajson).getString("dltMasterHotelEntityList");
                //数据可能为空 该页无数据，切换下一个城市
                List<SwitchMasterHotelVo> hotelData = (List<SwitchMasterHotelVo>) JSON.parseObject(hotelStr,List.class);
                if(null == hotelData || hotelData.size()==0){
                    //当前页面pageIndex查询结果无数据
                    //无数据没下一页，redis数据操作保存，保存成功后暂停定时器
                    return "none";
                }else{
                    Map<String,List<SwitchMasterHotelVo>> dataMap = new HashMap();
                    dataMap.put(UUID.randomUUID().toString(),hotelData);
                    //查询到数据，存入redis
                    boolean setResultRedis = setPutRedisWaitData(dataMap);
                    if(setResultRedis){
                        return "next";
                    }else{
                        //更新失败，不更新标识，下一次定时器继续查询当前
                        log.error("Switchs Hotel Master Job {} 携程酒店信息定时器 setResultRedis 异常错误: {},{}",timestamp,response,paramsJson);
                        return "error";
                    }
                }
            }else{
                //当前页面pageIndex查询结果无数据
                //无数据没下一页，redis数据操作保存，保存成功后暂停定时器
                return "none";
            }
        }else{
            //异常错误，redis城市id无变化，定时器下次进入继续查询当前酒店
            log.error("Switchs Hotel Master Job 获取携程母酒店更新异常错误: {},{}",response,paramsJson);
            return "error";
        }
    }

    /**
     * 加入 某城市id被查询标识
     * @param nowCityId
     */
    public void putHasCityMap(String nowCityId){
        Map<String,Object> has = getRedisHasCityIdsMap();
        if(null == has){
            has = new HashMap<>();
        }
        has.put(nowCityId,nowCityId);
        setRedisHasCityIdsMap(has);
        log.info("Switchs Hotel Master Job {} 获取携程母酒店 putHasCityMap has.size:{}",timestamp,has.size());
    }

    /**
     * 移出 某城市id被查询标识
     * @param nowCityId
     */
    public void removeHasCityMap(String nowCityId){
        Map<String,Object> has = getRedisHasCityIdsMap();
        if(null != has){
            has.remove(nowCityId);
        }
        setRedisHasCityIdsMap(has);
        log.info("Switchs Hotel Master Job {} 获取携程母酒店 removeHasCityMap has.size:{}",timestamp,has.size());
    }

    /**
     * 成功请求酒店房型接口后，合法酒店房型数据，追加进缓存中
     */
    private boolean setPutRedisWaitData(Map<String,List<SwitchMasterHotelVo>> mapRealRoomVoMap) {
        Map<String,List<SwitchMasterHotelVo>> getRedisMap = (Map<String,List<SwitchMasterHotelVo>>)redisService.get(redisWaitSaveKey);
        if(null != getRedisMap && getRedisMap.size()>0){
            getRedisMap.putAll(mapRealRoomVoMap);
        }else{
            getRedisMap = mapRealRoomVoMap;
        }
        boolean result = redisService.set(redisWaitSaveKey,getRedisMap);
        return result;
    }

    private void updateNextPage(int cityId) {
        int newIndex = Integer.valueOf(getOneCitySomeHotelPageIndex(cityId))+1;
        //更新当前城市酒店分页index标识
        setOneCitySomeHotelPageIndex(cityId,String.valueOf(newIndex));
    }

    /**
     * 更新删除redis记录城市id，，当前城市已没有下一页数据，下一次查询就是新的城市，则其他相关城市key需要置空
     */
    public void noneDataUpdateDeleteRedisHasCityMap(String saveUrl){
        log.info("Switchs Hotel Master Job {} 获取携程母酒店 noneDataUpdateDeleteRedisHasCityMap ",timestamp);
        String nowCityId = getRedisCityIdNow();
        deleteRedisCityIdNow();
        deleteRedisOneCityIdPageIndex(nowCityId);
        //判断是否已无下一个城市需要查询，则暂停定时器，删除相关城市id标识
        Map<String,Object> allCityIdsMap = getRedisAllCityIdsMap();
        Map<String, Object> resultMap = MapUtils.getDifferenceSet(allCityIdsMap,getRedisHasCityIdsMap());
        if(null == resultMap || resultMap.size()==0) {
            //已无待查询城市
            //调用保存redis数据，保存成功后暂停定时器
            boolean re = requestSaveWaitDataApi(saveUrl,getRedisWaitData());
            if(re){
                endingJob(true);
            }else{
                log.error("Switchs Hotel Master Job {} 最后一城市保存redis数据操作 异常错误: {},{}",timestamp);
            }

        }
    }
    /**
     * 结束定时器
     */
    public void endingJob(boolean delKey){
        //已最后一页，暂停定时器,删除rediskey
        JobForm jobForm = new JobForm();
        jobForm.setJobClassName(jobClassName);
        jobForm.setJobGroupName(jobGroupName);
        try {
            jobAndTriggerService.pauseJob(jobForm);
            if(delKey){
                if(!StringUtils.isEmpty(getRedisCityIdNow())){
                    deleteRedisOneCityIdPageIndex(getRedisCityIdNow());
                }
                deleteRedisCityIdNow();
                deleteRedisHasCityIdsMap();
                deleteRedisWaitSaveData();
            }
            log.warn("Switchs Hotel Master Job 执行暂停！: {},{}",jobGroupName,jobClassName);
        } catch (SchedulerException e) {
            log.error(e.getMessage(),e);
        }
    }

    public String getOneCitySomeHotelPageIndex(int cityId){
        String index = (String)redisTemplate.opsForValue().get(redisKeyHeadCityPageIndex+cityId);
        if(null == index){
            index  = "1";//接口参数要求  大于0  pageIndex 页码
            setOneCitySomeHotelPageIndex(cityId,index);
        }
        return index;
    }
    public void setOneCitySomeHotelPageIndex(int cityId,String newIndex){
        redisTemplate.opsForValue().set(redisKeyHeadCityPageIndex+cityId,newIndex);
    }
    public void deleteRedisOneCityIdPageIndex(String cityId){
        redisTemplate.delete(redisKeyHeadCityPageIndex+cityId);
    }
    public String getRedisCityIdNow(){
        return (String)redisService.get(timestamp+getRedisSwCityIdNow);
    }
    public void setRedisCityIdNow(String newCityId){
        redisService.set(timestamp+getRedisSwCityIdNow,newCityId,3600L);
    }
    public void deleteRedisCityIdNow(){
        redisTemplate.delete(timestamp+getRedisSwCityIdNow);
    }
    public void deleteRedisHasCityIdsMap(){
        redisTemplate.delete(redisSwCityIdsHas);
    }
    public Map<String,Object> getRedisAllCityIdsMap(){
        Map<String,Object> res = (Map<String,Object>)redisService.get(redisSwCityIds);
        return res;
    }
    public Map<String,Object> getRedisHasCityIdsMap(){
        Map<String,Object> res = (Map<String,Object>)redisService.get(redisSwCityIdsHas);
        return res;
    }
    public void setRedisHasCityIdsMap(Map<String,Object> hasCityIdsMap){
        redisService.set(redisSwCityIdsHas,hasCityIdsMap);
    }

}
